# Title: MultiListbox Tkinter widget
# Submitter: Brent Burley (with modifications by Eric Bogaerts/David O'Gwynn)
# Last Updated: 2001/03/14 (2007/05/15) (2008/04/14)
# Version no: 1.0 (1.1)
# Via: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52266

from Tkinter import *

class MultiListbox(PanedWindow):
    def __init__(self,master,lists,**kw):
        PanedWindow.__init__(self,master,
                             borderwidth=1,
                             #showhandle=False,
                             sashwidth=2,
                             sashpad=0,
                             relief=SUNKEN,
                             )
        self.lists = []
        self.columns=[]
        height = kw.get('height',10)
        font = kw.get('font',('Helvetica','10'))
        self.__command = kw.get('command',None)
        self.__doubleCommand = kw.get('double',None)
        label = kw.get('label','')
        if label:
            self._label = Label(
                master,text=label,
                borderwidth=1, relief=FLAT
                )
            self._label.pack(fill=X)
        totalWidth = 0
        for l,w in lists:
            totalWidth+=w
        for l,w in lists:
            self.columns.append(l)
            frame = Frame(self); frame.pack(side=LEFT, expand=YES, fill=BOTH)
            frame.bind('<MouseWheel>',lambda e,s=self:s._mousewheel(e))
            tl=Label(frame, text=l, borderwidth=2, relief=GROOVE,
                     font=font)
            tl.pack(fill=X)
            tl.bind('<Button-1>',self.clickon)
            lb = Listbox(frame, width=w, borderwidth=0,
                         selectborderwidth=0,relief=FLAT,
                         exportselection=FALSE,
                         height = height,
                         font=font,
                         selectmode=MULTIPLE ,bg='white')
            lb.pack(expand=YES, fill=BOTH)
            self.lists.append(lb)
	    lb.bind('<B1-Motion>', lambda e, s=self: s._select(e.y,e))
	    lb.bind('<Button-1>', lambda e, s=self: s._select(e.y,e))
	    lb.bind('<Control-Button-1>', lambda e, s=self: s._unselect(e.y))
	    lb.bind('<Double-Button-1>', lambda e, s=self: s._double(e.y))
	    lb.bind('<Leave>', lambda e: 'break')
	    lb.bind('<B2-Motion>', lambda e, s=self: s._b2motion(e.x, e.y))
	    lb.bind('<Button-2>', lambda e, s=self: s._button2(e.x, e.y))
            lb.bind(
                '<MouseWheel>',
                lambda e,s=self:s._scroll(SCROLL,
                                          -1*(1 if e.delta>0 else -1),
                                          UNITS))
            #lb.bind('<Button-4>',lambda e,s=self:s._mousewheel(e))
            #lb.bind('<Button-5>',lambda e,s=self:s._mousewheel(e))
            self.add(frame)
        sb = Scrollbar(master, orient=VERTICAL, command=self._scroll,borderwidth=1)
        sb.pack(fill=Y,side=RIGHT,expand=NO)
        for l in self.lists:
            l['yscrollcommand']=sb.set
        self.add(frame)
        self.pack(expand=YES,fill=BOTH)
        self.sortedBy=-1
        self.previousWheel=0
        self.bind('<Configure>',lambda e,s=self:s._configure(e))
        self.bind(
            '<MouseWheel>',
            lambda e,s=self:s._scroll(SCROLL,
                                      -1*(1 if e.delta>0 else -1),
                                      UNITS))
        self.bind('<Enter>',lambda e,s=self:s.focus_force())

    def _configure(self,event):
        pass

    __command = None
    __doubleCommand = None
    def _double(self, y):
        if self.__doubleCommand is not None:
            selection = list(self.curselection())
            if self.__indexMap is not None:
                for i,sel in enumerate(selection):
                    selection[i] = self.__indexMap[int(sel)]
            self.__doubleCommand(selection)
        return 'break'

    def _unselect(self, y):
        row = self.lists[0].nearest(y)
        self.selection_clear(row)
        if self.__command is not None:
            selection = list(self.curselection())
            if self.__indexMap is not None:
                for i,sel in enumerate(selection):
                    selection[i] = self.__indexMap[int(sel)]
            self.__command(selection)
        return 'break'
        
    def _select(self, y, event,state=16):
        row = self.lists[0].nearest(y)
#         print '--------------------------'
#         for attr in dir(event):
#             if attr[0] != "_":
#                 exec ('print "%s",event.%s'%(attr,attr))
#         print row,self.curselection()
        if state==16:self.selection_clear(0, END)
        self.selection_set(row)
        return 'break'


    def _button2(self, x, y):
        for l in self.lists: l.scan_mark(x, y)
        return 'break'


    def _b2motion(self, x, y):
        for l in self.lists: l.scan_dragto(x, y)
        return 'break'


    def _mousewheel(self, event):
        #print 'mousewheel',event.delta
        self._scroll(SCROLL,-1*(1 if event.delta>0 else -1),UNITS)
        return 'break'
    
    def _scroll(self, *args):
        for l in self.lists:
            apply(l.yview, args)
        return 'break'


    def clickon(self,e):
        self._sortBy(self.columns.index(e.widget['text']))


    __indexMap = None
    def _sortBy(self, column):
        """ Sort by a given column. """

        if column == self.sortedBy:
            direction = -1 * self.direction
        else:
            direction = 1

        elements = list(self.get(0, END))
        if self.__indexMap is None:
            self.__indexMap = range(len(elements))
        for i in range(len(elements)):
            elements[i] = list(elements[i])
            elements[i].append(self.__indexMap[i])
        self.delete(0, END)
        elements.sort(lambda x, y: self._sortAssist(column, direction, x, y))
        for i in range(len(elements)):
            self.__indexMap[i] = elements[i].pop()
        print self.__indexMap
        self.insert(END, elements)

        self.sortedBy = column
        self.direction = direction


    def _sortAssist(self, column, direction, x, y):
        tx,ty = x[column],y[column]
        if x[column].isdigit():
            tx = int(x[column])
        elif tx.count('.')==3: # IP address
            tx = map(int,tx.split('.'))
        if y[column].isdigit():
            ty = int(y[column])
        elif ty.count('.')==3: # IP address
            ty = map(int,ty.split('.'))
        c = cmp(tx,ty)
        if c:
            return direction * c
        else:
            return direction * cmp(tx, ty)

    def curselection(self):
        return self.lists[0].curselection()


    def delete(self, first, last=None):
        for l in self.lists:
            l.delete(first, last)


    def get(self, first, last=None):
        result = []
        for l in self.lists:
            result.append(l.get(first,last))
        if last: return apply(map, [None] + result)
        return result

        
    def index(self, index):
        self.lists[0].index(index)


    def insert(self, index, elements,indexHash=None):
        if indexHash is not None:
            if self.__indexMap is None:
                self.__indexMap = []
            self.__indexMap.append(indexHash)
#         for e in elements:
#             i = 0
#             for l in self.lists:
#                 l.insert(index, e[i])
#                 i = i + 1
        if type(elements[0]) in (type(()),type([])):
            for e in elements:
                for i,l in enumerate(self.lists):
                    l.insert(index, e[i])
        else:
            for i,l in enumerate(self.lists):
                l.insert(index, elements[i])


    def size(self):
        return self.lists[0].size()


    def see(self, index):
        for l in self.lists:
            l.see(index)

    def selection_anchor(self, index):
        for l in self.lists:
            l.selection_anchor(index)


    def selection_clear(self, first, last=None):
        for l in self.lists:
            l.selection_clear(first, last)


    def selection_includes(self, index):
        return self.lists[0].selection_includes(index)


    def clear(self):
        self.__indexMap = None
        self.delete(0,END)

    def selection_set(self, first, last=None):
        for l in self.lists:
            l.selection_set(first, last)
        if self.__command is not None:
            selection = list(self.curselection())
            if self.__indexMap is not None:
                for i,sel in enumerate(selection):
                    selection[i] = self.__indexMap[int(sel)]
            self.__command(selection)

if __name__ == '__main__':
    tk = Tk()
    Label(tk, text='MultiListbox').pack()
    mlb = MultiListbox(tk, (('Subject', 40), ('Sender', 20), ('Date', 10)))
    for i in range(1000):
	mlb.insert(END, ('Important Message: %d' % i, 'John Doe', '10/10/%04d' % (1900+i)))
    mlb.pack(expand=YES,fill=BOTH)
    tk.mainloop()

